package queryTree;
/**
 * @author Van
 */
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;

import optimization.Condition;
import optimization.Field;
import optimization.Parser;
import optimization.SQLParsingException;
import optimization.tab_name_att_name;

public class Main {
	public static TreeNode getTreeFromSQL(String sql) throws SQLParsingException {

		TreeNode result = null;
		Parser parser = new Parser();
		
			parser.parse(sql);
			LinkedList<Field> temp_TableList = parser.getTable_List();
			ArrayList<String> org_TableList = new ArrayList<String>();
			LinkedList<Condition> joinList = parser.getJoin_List();
			LinkedList<Field> projectionList = parser.getSelect_List();
			LinkedList<Condition> boolExprList = parser.getBooleanExp_List();
			
			
			Iterator<Field> iter = temp_TableList.iterator();
			while(iter.hasNext()) {
				Field f = iter.next();
				org_TableList.add(f.table_name.toString());
			}

			//group join conditions which belongs to one join operation.
			LinkedList<TableCouple> groups = new LinkedList<TableCouple>();
			for (int i = 0; i < joinList.size(); i++) {
				Condition con = joinList.get(i);

				if (!temp_TableList.contains(con.lhs)) {
					throw new SQLParsingException(con.lhs.item_name
							+ "is not an attribute in table list");
				} else if (!temp_TableList.contains(con.rhs)) {
					throw new SQLParsingException(con.rhs.item_name
							+ "is not an attribute in table list");
				}
				TableCouple tc = new TableCouple(con.lhs.table_name.toString(),
						con.rhs.table_name.toString());

				int j = groups.indexOf(tc);
				if (j == -1) {
					tc.addCondition(con);
					groups.add(tc);
				} else {
					if (!groups.get(j).getJoinConditionList().contains(con)) {
						groups.get(j).getJoinConditionList().add(con);
					}
				}
			}
			
			//group boolean expr of tables
			HashMap<String, BooleanExpr> boolExpMap = groupBoolenExpr(boolExprList);		
			
			//from groups of TableCouple, create Tree with JoinNodes            
			for (int i = 0; i < groups.size(); i++) {
				if (i == 0) {
					TableCouple tc = groups.get(0);
					//for table 1
					TableNode leftTableNode = new TableNode(tc.getTable1());
					//get boolean expression (push down) for table 1
					leftTableNode.setSelectPredicate(boolExpMap.get(tc.getTable1()));
					
					//for table 2
					TableNode rightTableNode = new TableNode(tc.getTable2());
					//get boolean expression (push down) for table 2
					rightTableNode.setSelectPredicate(boolExpMap.get(tc.getTable2()));
					
					//create join Node for two tables
					JoinNode joinNode = new JoinNode();
					joinNode.setLeftJoinTree(leftTableNode);
					joinNode.setRightJoinTree(rightTableNode);
					joinNode.setJoinConditionList(tc.getJoinConditionList());

					result = joinNode;

					//done for two tables, remove it out of the query table list
					ArrayList<Field> removeL = new ArrayList<Field>();
					Iterator<Field> iterField = temp_TableList.iterator();
					while (iterField.hasNext()) {
						Field table = iterField.next();
						if (table.table_name.toString().equals(tc.getTable1())
								|| table.table_name.toString().equals(
										tc.getTable2())) {
							removeL.add(table);
						}
					}
					temp_TableList.removeAll(removeL);

				} else {
					TableCouple tc = groups.get(i);
					//get all tables in tree
					ArrayList<String> treeTableList = new ArrayList<String>();
					result.getTableList(treeTableList);
					//if two tables in table couple are both in one tree --> add more join conditions,
					//do not create new tree.
					if (treeTableList.contains(tc.getTable1())
							&& treeTableList.contains(tc.getTable2())) {
						if (result instanceof JoinNode) {
							((JoinNode) result).addJoinConditions(tc
									.getJoinConditionList());
						}
					} else if (treeTableList.contains(tc.getTable1())) {
						//Need to create join Node between the old tree and table 2
						
						TableNode rightTableNode = new TableNode(tc.getTable2());
						//get boolean expression (push down) for table 2
						rightTableNode.setSelectPredicate(boolExpMap.get(tc.getTable2()));
						
						JoinNode joinNode = new JoinNode();
						joinNode.setLeftJoinTree(result);
						joinNode.setRightJoinTree(rightTableNode);
						joinNode.setJoinConditionList(tc.getJoinConditionList());

						result = joinNode;
						
					} else if (treeTableList.contains(tc.getTable2())) {
						//Need to create join Node between the old tree and table 2
						TableNode rightTableNode = new TableNode(tc.getTable1());
						//get boolean expression (push down) for table 2
						rightTableNode.setSelectPredicate(boolExpMap.get(tc.getTable1()));
												
						JoinNode joinNode = new JoinNode();
						joinNode.setLeftJoinTree(result);
						joinNode.setRightJoinTree(rightTableNode);
						joinNode.setJoinConditionList(tc.getJoinConditionList());

						result = joinNode;
					} else {
						//two tables are not in the tree --> need to create new join node and then merge
						//for table 1
						TableNode leftTableNode = new TableNode(tc.getTable1());
						//get boolean expression (push down) for table 1
						leftTableNode.setSelectPredicate(boolExpMap.get(tc.getTable1()));
						
						//for table 2
						TableNode rightTableNode = new TableNode(tc.getTable2());
						//get boolean expression (push down) for table 2
						rightTableNode.setSelectPredicate(boolExpMap.get(tc.getTable2()));
						
						//join two tables
						JoinNode joinNode1 = new JoinNode();
						joinNode1.setLeftJoinTree(leftTableNode);
						joinNode1.setRightJoinTree(rightTableNode);
						joinNode1.setJoinConditionList(tc
								.getJoinConditionList());

						//join the new join node with a tree
						JoinNode joinNode2 = new JoinNode();
						joinNode2.setLeftJoinTree(result);
						joinNode2.setRightJoinTree(joinNode1);
						joinNode2.setJoinConditionList(tc
								.getJoinConditionList());

						result = joinNode2;
					}

					//	done for two tables, remove it out of the query table list
					ArrayList<Field> removeL = new ArrayList<Field>();
					Iterator<Field> iterField = temp_TableList.iterator();
					while (iterField.hasNext()) {
						Field table = iterField.next();
						if (table.table_name.toString().equals(tc.getTable1())
								|| table.table_name.toString().equals(
										tc.getTable2())) {
							removeL.add(table);
						}
					}
					temp_TableList.removeAll(removeL);
				}
			}

			// check to see if there is some table left in tableList.
			// if yes, it is a cartesian.
			if (temp_TableList.size() != 0) {
				if (result == null) {//there is no join condition
					for (int i = 0; i < temp_TableList.size(); i++) {
						String tableName = temp_TableList.get(i).table_name.toString();
						if (i == 0) {
							
							TableNode tableNode = new TableNode(tableName);
							//get boolean expression (push down)
							tableNode.setSelectPredicate(boolExpMap.get(tableName));
							
							result = tableNode;
						} else {
							TableNode tableNode = new TableNode(tableName);
							//get boolean expression (push down)
							tableNode.setSelectPredicate(boolExpMap.get(tableName));
							
							JoinNode joinNode = new JoinNode();
							joinNode.setLeftJoinTree(result);
							joinNode.setRightJoinTree(tableNode);

							result = joinNode;
						}
					}
				} else {
					for (int i = 0; i < temp_TableList.size(); i++) {
						String tableName = temp_TableList.get(i).table_name.toString();;
						TableNode tableNode = new TableNode(tableName);
						//get boolean expression (push down)
						tableNode.setSelectPredicate(boolExpMap.get(tableName));
						
						
						JoinNode joinNode = new JoinNode();
						joinNode.setLeftJoinTree(result);
						joinNode.setRightJoinTree(tableNode);

						result = joinNode;

					}
				}
			} //done for creating a tree and push the selection down.
			
			//push projection down
			HashMap<String, ArrayList<Field>> map = groupProjection(projectionList);
			if(!org_TableList.containsAll(map.keySet())){
				throw new SQLParsingException("Some attribute(s) in the SELECT clause " +
						"does not belongs to the table lists in FROM clause");
			} else {
				pushProjectionDown(map, result);
			}
			
		
		return result;
	}
	
	private static HashMap<String, BooleanExpr> groupBoolenExpr(LinkedList<Condition> l) {
		HashMap<String, BooleanExpr> result = new HashMap<String, BooleanExpr>();
		Iterator<Condition> iter = l.iterator();
		while(iter.hasNext()) {
			Condition con = iter.next();
			String tableName = "";
			SimpleBoolExpr sBool = null;
			if(con.lhs.table_name != tab_name_att_name.NULL) {//attr op vale
				tableName = con.lhs.table_name.toString();
				sBool = new SimpleBoolExpr();
				sBool.setAttr(con.lhs);
				sBool.setOperator(con.operator.ordinal());
				sBool.setValue(con.rhs_s);
				
			} else if(con.rhs.table_name != tab_name_att_name.NULL) {//value op attr
				tableName = con.rhs.table_name.toString();
				sBool = new SimpleBoolExpr();
				sBool.setAttr(con.rhs);
				sBool.setOperator(con.operator.ordinal());
				sBool.setValue(con.lhs_s);				
			}
			if(result.containsKey(tableName)) {
				And a = new And(result.get(tableName), sBool);
				result.put(tableName, a);
			} else {
				result.put(tableName, sBool);
			}
		}		
		return result;
	}	
	
	private static HashMap<String, ArrayList<Field>> groupProjection(LinkedList<Field> l) {
		HashMap<String, ArrayList<Field>> result = new HashMap<String, ArrayList<Field>>();
		Iterator<Field> iter = l.iterator();
		while(iter.hasNext()) {
			Field f = iter.next();
			if(result.containsKey(f.table_name.toString())) {
				result.get(f.table_name.toString()).add(f);
			} else {
				ArrayList<Field> fl = new ArrayList<Field>();
				fl.add(f);
				result.put(f.table_name.toString(), fl);
			}
			
		}
		return result;
	}
	private static void pushProjectionDown(HashMap<String, ArrayList<Field>> map, TreeNode t) {
		if (t instanceof TableNode) {
			String tableName = ((TableNode)t).getTableName();
			((TableNode) t).setAttrsProjection(map.get(tableName));			
		} else if(t instanceof JoinNode) {
			ArrayList<Field> fieldList = new ArrayList<Field>();
			Iterator<ArrayList<Field>> iter = map.values().iterator();
			while(iter.hasNext()) {
				ArrayList<Field> fl = iter.next();
				fieldList.addAll(fl);
			}
			//set attribute projections for join node
			((JoinNode)t).setAttrsProjection(fieldList);
			//get join conditions
			ArrayList<Condition> conL = ((JoinNode)t).getJoinConditionList();
			
			//push down left node and right node
			//left node
			TreeNode leftTree = ((JoinNode)t).getLeftJoinTree();
			ArrayList<String> leftTableList = new ArrayList<String>();
			leftTree.getTableList(leftTableList);	
			HashMap<String, ArrayList<Field>> leftMap = new HashMap<String, ArrayList<Field>>();
			Iterator<String> iter1 = leftTableList.iterator();
			while(iter1.hasNext()) {
				String tableName = iter1.next();
				if(map.containsKey(tableName)) {
					leftMap.put(tableName, map.get(tableName));
				}				
			}
			
			
			TreeNode rightTree = ((JoinNode)t).getRightJoinTree();
			ArrayList<String> rightTableList = new ArrayList<String>();
			rightTree.getTableList(rightTableList);
			HashMap<String, ArrayList<Field>> rightMap = new HashMap<String, ArrayList<Field>>();
			Iterator<String> iter2 = rightTableList.iterator();
			while(iter2.hasNext()) {
				String tableName = iter2.next();
				if(map.containsKey(tableName)) {
					rightMap.put(tableName, map.get(tableName));
				}

			}
			//add projection for join attributes
			for(int i = 0; i< conL.size(); i++) {
				Condition con = conL.get(i);
				Field lf = con.lhs;
				Field rf = con.rhs;
				if(leftTableList.contains(lf.table_name.toString())){
					if(leftMap.containsKey(lf.table_name.toString())) {
						if(!leftMap.get(lf.table_name.toString()).contains(lf)){
							leftMap.get(lf.table_name.toString()).add(lf);
						}
					} else {
						ArrayList<Field> fl = new ArrayList<Field>();
						fl.add(lf);
						leftMap.put(lf.table_name.toString(), fl);
					}
				} 
				if(leftTableList.contains(rf.table_name.toString())){
					if(leftMap.containsKey(rf.table_name.toString())) {
						if(!leftMap.get(rf.table_name.toString()).contains(rf)){
							leftMap.get(rf.table_name.toString()).add(rf);
						}
						
					} else {
						ArrayList<Field> fl = new ArrayList<Field>();
						fl.add(rf);
						leftMap.put(rf.table_name.toString(), fl);
					}
				}
				if(rightTableList.contains(lf.table_name.toString())) {
					if(rightMap.containsKey(lf.table_name.toString())) {
						if(!rightMap.get(lf.table_name.toString()).contains(lf)){
							rightMap.get(lf.table_name.toString()).add(lf);
						}
					} else {
						ArrayList<Field> fl = new ArrayList<Field>();
						fl.add(lf);
						rightMap.put(lf.table_name.toString(), fl);
					}
				}
				if(rightTableList.contains(rf.table_name.toString())) {
					if(rightMap.containsKey(rf.table_name.toString())) {
						if(!rightMap.get(rf.table_name.toString()).contains(rf)){
							rightMap.get(rf.table_name.toString()).add(rf);
						}
						
					} else {
						ArrayList<Field> fl = new ArrayList<Field>();
						fl.add(rf);
						rightMap.put(rf.table_name.toString(), fl);
					}
				}
			}
			
			pushProjectionDown(leftMap, leftTree);
			pushProjectionDown(rightMap, rightTree);
			
		}
	}
	
	
	
}
